<template>
    <div :class="['dci-form-wrap',{'d-n':formState==='error'}]">
        <Form
            class="dci-form"
            ref="dciForm"
            :model="nConfig"
            :label-position="labelPosition"
            :label-width="labelWidth"
            :disabled="disabled"
            action
            inline
        >
            <div class="form-module" v-for="(mainmodule,m) in nConfig">
                <template v-if="mainmodule.title">
                    <h1 class="module-main-title" v-html="mainmodule.title"></h1>
                </template>

                <template v-for="(submodule,s) in mainmodule.submodule">
                    <div class="form-module-item">
                        <div class="module-sub-row">
                            <template v-if="submodule.title">
                                <h3 class="module-sub-title" v-html="submodule.title"></h3>
                            </template>

                            <template v-if="submodule.tableSlotBtn">
                                <div class="table-in-btn" :style="submodule.tableSlotBtn.style">
                                    <Button
                                        v-for="(btn,text,index) in submodule.tableSlotBtn.btn"
                                        :key="index"
                                        @click.native="handleEventsCenter(btn,submodule)"
                                    >{{ text }}</Button>
                                </div>
                            </template>
                        </div>

                        <template v-if="submodule.table">
                            <div class="module-sub-table">
                                <table border>
                                    <thead>
                                        <tr>
                                            <th
                                                v-for="(thead,h) in submodule.table.thead"
                                                v-show="!thead.isHidden"
                                                :style="{width: thead.width}"
                                            >{{ thead.title }}</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <tr v-for="(tbody,b) in submodule.table.tbody">
                                            <td v-for="(field,key,index) in tbody" v-show="!submodule.table.thead[index].isHidden">
                                                <DCI-Field
                                                    v-if="field.constructor==Object"
                                                    :field="field"
                                                    :prop="`${m}.submodule.${s}.table.tbody.${b}.${key}.default`"
                                                    :param="{ 
                                                        index: b,
                                                        field:field,
                                                        row: tbody,
                                                        table:submodule.table, 
                                                        submodule:submodule, 
                                                        mainmodule:mainmodule, 
                                                        config: nConfig
                                                    }"
                                                    :keys="key"
                                                    :key="key"
                                                    :getValidateRule="getValidateRule"
                                                ></DCI-Field>
                                                <DCI-Field
                                                    v-if="field.constructor==Array"
                                                    :style="'width:'+100/field.length+'%'"
                                                    v-for="(row, index) in field"
                                                    :field="row"
                                                    :prop="`${m}.submodule.${s}.table.tbody.${b}.${key}.default`"
                                                    :param="{ 
                                                        index: b,
                                                        field:row,
                                                        row: tbody,
                                                        table:submodule.table, 
                                                        submodule:submodule, 
                                                        mainmodule:mainmodule, 
                                                        config: nConfig
                                                    }"
                                                    :keys="key+index"
                                                    :key="key+index"
                                                    :getValidateRule="getValidateRule"
                                                ></DCI-Field>
                                                <template v-if="field.sub">
                                                    <div
                                                        class="sub-field"
                                                        v-for="(subfield,sub) in field.sub"
                                                    >
                                                        <DCI-Field
                                                            :field="subfield"
                                                            :prop="`${m}.submodule.${s}.table.tbody.${b}.${key}.sub.${sub}.default`"
                                                            :param="{ 
                                                                index: b,
                                                                field:field,
                                                                row: tbody[b],
                                                                table:submodule.table, 
                                                                submodule:submodule, 
                                                                mainmodule:mainmodule, 
                                                                config: nConfig
                                                            }"
                                                            :keys="sub"
                                                            :key="sub"
                                                            :getValidateRule="getValidateRule"
                                                        ></DCI-Field>
                                                    </div>
                                                </template>
                                            </td>
                                        </tr>
                                    </tbody>
                                </table>
                            </div>
                        </template>

                        <template v-if="submodule.form">
                            <div class="module-sub-form" v-if="submodule.form">
                                <DCI-Field
                                    v-for="(field,key) in submodule.form"
                                    :field.sync="field"
                                    :label-position="labelPosition"
                                    :label-width="labelWidth"
                                    :prop="`${m}.submodule.${s}.form.${key}.default`"
                                    :param="{ 
                                        field:field,
                                        form:submodule.form, 
                                        submodule:submodule, 
                                        mainmodule:mainmodule, 
                                        config: nConfig
                                    }"
                                    :keys="key"
                                    :key="key"
                                    :getValidateRule="getValidateRule"
                                ></DCI-Field>
                            </div>
                        </template>

                        <template v-if="submodule.tips">
                            <div class="module-sub-tips" v-html="submodule.tips"></div>
                        </template>
                    </div>
                </template>
            </div>
            <slot name="dci-form"></slot>
        </Form>
    </div>
</template>

<script>
/**
 *  @description 根据匹配的对象, 遍历数组, 找到含有完全键值对的数据值
 *  @param {Array} target 目标数组
 *  @param {Object} matcg 匹配对象
 *  @return 一个新的数组
 */
let matchObject = (target = [], match = {}) => {
    for (const key in match) {
        target = target.filter(t => JSON.stringify(t).replace(/\"/g, '\'').indexOf(`'${key}':'${match[key]}'`) > -1);
    }

    return target;
};

// 类型判断
let toString = target => Object.prototype.toString.call(target).slice(8, -1);

// 字段转小写
let toLowerCase = (target) => {
    let type = toString(target);
    let result;

    if (type == 'Array') {
        result = [];
        target.forEach((item) => {
            result.push(toLowerCase(item))
        });
    } else if (type == 'Object') {
        result = {};
        for (const key in target) {
            let nKey = key.toLowerCase();
            result[nKey] = toLowerCase(target[key]);
        }
    } else {
        result = target;
    }
    return result;
};

import "@/assets/js/prototype.extension.js";
import Field from "@/components/form/Field";
import validate from '@/assets/js/validate';
export default {
    name: 'DCIForm',
    data () {
        return {
            // 表单状态: error-错误
            formState: null,
            // 等同于props的config, props是不允许修改, 但此处定义的config是对象结构, 所以修改了也不会报错
            nConfig: this.config,
            // 等同于props的data
            nData: this.data,
            // 下拉框回填信息收集
            selectOptionInfo: [],
            // 记录配置结构变化次数
            configCount: 0,
            // 校验规则
            getValidateRule: validate,
        }
    },
    props: {
        // 配置结构
        config: {
            type: Object,
            default: {},
        },
        // 表单数据
        data: {
            type: Object,
            default: {},
        },
        // 是否禁用该表单内的所有组件
        disabled: {
            type: Boolean,
            default: false,
        },
        // 表单域标签的位置
        'label-position': {
            type: String,
            // left 为左对齐，right 为右对齐，top 会置于表单域顶部
            default: 'right'
        },
        // 表单域标签的宽度
        'label-width': {
            type: Number,
        },
    },
    watch: {
        nConfig: {
            handler (n, o) {
                ++this.configCount;
                this.$emit('on-watch-config', n, o);
            },
            deep: true
        },
        nData: {
            handler (n, o) {

            },
            deep: true
        },
        'configCount' (n) {
            if (n % 100 === 0) {
                console.log('【表单配置结构】发生变动次数：' + n);
            }
        }
    },
    
    components: {
        Field
    },
    created () {

    },
    beforeMount () {
        // 暂时兼容在含有layui版本项目中引用DCIForm所造成的错误
        if ( this.imodal ) {
            this.modal = this.imodal;
        }

        this.$emit('on-before', this.nConfig, this.nData);
    },
    mounted () {
        this.modal.load();
        new Promise((resolve, reject) => {
            // 第一步: 遍历配置结构, 过滤下拉框配置（含有database字段的）
            let res = {
                // 下拉框请求服务收集, 声明为对象, 是因为可以过滤掉重复的请求
                selectAxios: {},
                // 下拉框回填信息收集
                selectOptionInfo: []
            };
            // 循环执行字段过滤
            let iteratorFilter = (item) => {
                for (const k in item) {
                    let field = item[k];

                    res = this.filterField(res, field,k);

                    if (field.sub) {
                        iteratorFilter(field.sub);
                    }
                }
            };

            this.iteratorNConfig({
                nConfig: this.nConfig,
                tableOnCallback: (table) => {
                    
                    table.tbody.forEach((item) => {
                        iteratorFilter(item);
                    });

                    if (table.tpl) {
                        iteratorFilter(table.tpl);
                    }
                },
                formOnCallback: (form) => {
                    iteratorFilter(form);
                },
            });

            resolve(res);
        }).then((res) => {
            // 第二步: 根据selectAxios, 通过axios.all, 集中请求下拉框服务, 并格式化相关的下拉框结构的数据
            let { selectAxios, selectOptionInfo } = res;

            return this.$ajax.all(Object.values(selectAxios)).then(this.$ajax.spread((...data) => {
                let selectAllOption = {};
                let filterAllOption = {};

                data.forEach((item,index) => {
                    if ( !item ) { return; }
                    if (!selectAllOption[item.lx]) {
                        let filterResult = [];
                        switch (item.type) {
                            case 'select':
                            case 'select2':
                            case 'select6':
                                item.option.forEach((opt) => {
                                    filterResult.push({
                                        value: opt.BMZ,
                                        text: opt.BMMC
                                    });
                                });
                                break;

                            case 'select3':
                                // example: 土地用途
                                item.option.forEach((opt) => {
                                    for (const key in opt) {
                                        filterResult.push({
                                            label: key,
                                            item: opt[key].length > 0 ? opt[key] : []
                                        });
                                    }
                                });

                                break;

                            case 'select4':
                                // example: 重庆外网的房屋用途
                                item.option.forEach((opt) => {
                                    filterResult.push({
                                        label: opt['BMMC'],
                                        value: opt['BMZ'],
                                        item: opt['CHILDREN'].length > 0 ? opt['CHILDREN'] : []
                                    });
                                });

                                break;
                            case 'select5':
                                // example: 重庆外网的银行授权,自定义数据形式
                                const SELECTFIELD = selectOptionInfo[index].field;

                                // 示例代码
                                // SELECTFIELD.filter = function (options){
                                //     var DEEPFILTER = function (arr) {
                                //         return arr.map(item=>{
                                //             return {
                                //                 label: item.orgName,
                                //                 value: item.orgName,
                                //                 item: item['children'].length > 0 ? DEEPFILTER(item['children']) : []
                                //             }
                                //         });
                                //     };
                                //     return DEEPFILTER(options);
                                // };
                                
                                filterResult = SELECTFIELD.filter(item.option);

                                break;

                            default:
                                break;
                        }

                        selectAllOption[item.lx] = filterResult;
                    }
                });
                return {
                    selectAxios,
                    selectOptionInfo,
                    selectAllOption
                }
            }));
        }).then((res) => {
            // 第三步: 并回填对应字段的option值
            let { selectAxios, selectOptionInfo, selectAllOption } = res;
            
            selectOptionInfo.forEach((info) => {
                if ( info.field.cascader ) { 
                    
                    return; 
                };
                let field = info.field;
                if (field.select.database) {
                    var lx = field.select.database;
                }else if (field.select.fwmc) {
                    var lx = field.select.fwmc;
                }

                this.$set(info.field, 'option', selectAllOption[lx])
            });
            return;
        }).then(() => {
            // 第四步: 表单数据写入、执行加载后事件
            return new Promise((resolve) => {
                this.$nextTick(() => {
                    this.toWirteFormData();
                    resolve();
                });
            })
        }).then(() => {
            // 第五步: 执行加载后事件
            return new Promise((resolve) => {
                this.$nextTick(() => {
                    this.$emit('on-mounted', this.nConfig, this.nData);
                    this.modal.close();
                });
            })
        }).catch(err => {
            console.log(err);
            this.modal.alert({
                content: `动态表单创建失败。错误原因如下：${err}<br/>请联系管理员。`,
                icon: 'warning',
                offset: ['30%'],
                success: () => {
                    this.formState = 'error';
                    this.modal.close();
                }
            });
        });
    },
    updated () {

    },
    methods: {
        // 下拉框请求服务
        getSelectServer (lx, type) {
            return this.$ajax.post(DCIConfig.wwsq_narmalCode, {
                jsondt: {
                    lx: lx
                }
            }).then((data) => {
                if (data.zt == "1") {
                    return {
                        lx: lx,
                        type: type,
                        option: data.dt.datas || data.dt
                    };
                } else {
                    this.modal.alert({ content: `下拉框为<span style="padding:0 10px;color:#e91e63;font-weight:550;">${lx}</span>的请求发生错误，如下：${data.msg}` });
                    return {
                        lx: lx,
                        type: type,
                        option: []
                    };
                }
            });
        },
        // 下拉框自定义请求服务
        getCustomSelectServer (fwmc, jsondt, type) {
            return this.$ajax.post(DCIConfig[fwmc], {
                jsondt: jsondt
            }).then((data) => {
                if (data.zt == "1") {
                    return {
                        lx: fwmc,
                        type: type,
                        option: data.dt.datas || data.dt
                    };
                } else {
                    this.modal.alert({ content: `下拉框为<span style="padding:0 10px;color:#e91e63;font-weight:550;">${lx}</span>的请求发生错误，如下：${data.msg}` });
                    return {
                        lx: fwmc,
                        type: type,
                        option: []
                    };
                }
            });
        },
        // 联动请求服务
        getCascaderServer (pid, field) {
            let url = DCIConfig.wwsq_sjld;
            switch (field.cascader) {
                case 'swprovince':
                case 'swcity':
                case 'swcountry':
                    url = DCIConfig.wwsq_swjgsjld;
                    break;
            }

            return this.$ajax.post(url, {
                jsondt: {
                    ID: pid
                }
            }).then((data) => {
                if (data.zt == "1") {
                    let dt = data.dt || [];
                    dt = dt.map((item)=>{
                        return {
                            value: item.ID,
                            text: item.MC
                        }
                    });
                    
                    this.$set(field,'option',dt);
                } else {
                    this.modal.alert({ content: `联动请求发生错误，如下：${data.msg}` });
                }
            });
        },
        // 字段过滤
        filterField (res, field, key) {
            switch (field.type) {
                case 'select':
                case 'select2':
                case 'select3':
                case 'select4':
                case 'select5':
                case 'select6':
                    if ( field.cascader ) {
                        // 三级联动配置
                        let pid = '';
                        let prevCascader = null;
                        let nConfig = this.global.deepClone(this.nConfig);
                        switch ( field.cascader ) {
                            case 'city':
                                prevCascader = 'province';
                                break;
                            case 'country':
                                prevCascader = 'city';
                                break;
                            case 'swcity':
                                prevCascader = 'swprovince';
                                break;
                            case 'swcountry':
                                prevCascader = 'swcity';
                                break;
                        
                            default:
                                prevCascader = '';
                                break;
                        }
                        this.iteratorNConfig({
                            nConfig: nConfig,
                            tableOnCallback: (table) => {

                            },
                            formOnCallback: (form) => {
                                for (const key in form) {
                                    let f = form[key];
                                    if ( f.cascader === prevCascader ) {
                                        pid = this.nData[key];
                                    }
                                }
                            }
                        });

                        if ( !res.selectAxios[field.cascader] ) {
                            res.selectAxios[field.cascader] = this.getCascaderServer(pid, field);
                        }

                    } else if ( field.select && field.select.database ) {
                        const LX = field.select.database;

                        res.selectOptionInfo.push({
                            lx: LX,
                            field: field,
                        });
                        if (!res.selectAxios[LX]) {
                            res.selectAxios[LX] = this.getSelectServer(LX, field.type);
                        }
                    } else if (field.select && field.select.fwmc) { // 下拉框请求自定义服务
                        const LX = field.select.fwmc;
                        res.selectOptionInfo.push({
                            lx: LX,
                            field: field,
                        });
                        if (!res.selectAxios[LX]) {
                            res.selectAxios[LX] = this.getCustomSelectServer(field.select.fwmc, field.select.jsondt, field.type);
                        }
                    }
                    
                    break;

                default:
                    break;
            }
            return res;
        },
        // 针对传入的配置结构进行迭代, 并根据相关回调进行配置结构的处理
        iteratorNConfig ({ nConfig, tableOnCallback, formOnCallback }) {
            for (const key in nConfig) {
                let submodule = nConfig[key].submodule;

                submodule.forEach((subItem, i) => {
                    // 动态表格类型配置
                    if (subItem.hasOwnProperty('table')) {
                        tableOnCallback && tableOnCallback(subItem.table);
                    }

                    // 动态表单类型配置
                    if (subItem.hasOwnProperty('form')) {
                        formOnCallback && formOnCallback(subItem.form);
                    }
                })
            }
            return;
        },
        /** 
         *  @notice 此方法主要也暴露给父组件进行调用
         *  @description 表单数据写入, 一旦调用会重新对表单的结构进行数据写入
         */
        toWirteFormData () {
            // 循环执行字段写入数据, 先写入主字段, 再写入子字段
            let iteratorWrite = (item, data) => {
                for (const m in item) {
                    if (item[m] && data[m]) {
                        this.$set(item[m], 'default', data[m]);
                    }

                    if (item[m].sub) {
                        for (const n in item[m].sub) {
                            if (item[m].sub[n] && data[n]) {
                                this.$set(item[m].sub[n], 'default', data[n]);
                            }
                        }
                    }
                }
            };

            this.iteratorNConfig({
                nConfig: this.nConfig,
                tableOnCallback: (table) => {
                    // 这里有一条规则: table.param的字段必须是提交到数据库的
                    // 亦就是说所有的param都是不同的
                    const FIELD = table.field;
                    const PARAMS = table.param || {};
                    let theTableData = [];

                    if (!this.nData[FIELD] || this.nData[FIELD].length === 0) { return };

                    theTableData = matchObject(this.nData[FIELD], PARAMS);
                    theTableData.forEach((data, index) => {
                        // 先增后写入
                        if (!table.tbody[index]) {
                            let tpl = table.tbody[0];
                            if (table.tpl) {
                                tpl = table.tpl;
                            }
                            table.tbody.push(this.global.deepClone(tpl));
                        }
                        iteratorWrite(table.tbody[index], data);
                    });
                },
                formOnCallback: (form) => {
                    iteratorWrite(form, this.nData);
                },
            });
        },
        /** 
         *  @notice 此方法主要给父组件进行调用
         *  @description 获取表单提交字段（根据配置结构, 提取含有default名称的字段）
         *  @function  toFieldsDefault // 获取指定对象结构的default键值
         */
        toGetSubmitData () {
            let res = {};
            let nConfig = this.global.deepClone(this.nConfig);
            let toFieldsDefault = (item) => {
                let fields = {};
                for (const key in item) {
                    let field = item[key];
                    // 默认值存在、属于提交字段 为字段提交条件
                    if (field.default && field.isCommit !== false) {
                        if (field.default instanceof Date) {
                            field.default = field.default.format('yyyy-MM-dd');
                        }
                        fields[key] = field.default;
                    } else {
                        // 由于表单数据为空, 用户亦没有填写数据, 此时无default值,但提交所需, 故要默认为空
                        fields[key] = null
                    }

                    // 存在子字段
                    if (field.sub) {
                        fields = Object.assign(fields, toFieldsDefault(field.sub));
                    }
                }
                return fields;
            };

            this.iteratorNConfig({
                nConfig: nConfig,
                tableOnCallback: (table) => {
                    // 提交字段名称全部转小写
                    const FIELD = table.field;
                    const PARAMS = table.param || {};
                    const TABLEFIELD = [];

                    table.tbody.forEach((item) => {
                        // 每一行的提交字段, 并且合并param
                        const ROW = Object.assign(toFieldsDefault(item), PARAMS);
                        TABLEFIELD.push(ROW);
                    });

                    res[FIELD] = res[FIELD] ? res[FIELD].concat(TABLEFIELD) : TABLEFIELD;
                },
                formOnCallback: (form) => {
                    res = Object.assign(res, toFieldsDefault(form));
                }
            });

            return toLowerCase(res);
        },
        // 按钮事件调度中心
        handleEventsCenter (btn,submodule) {
            const TYPEOF = Object.prototype.toString.call(btn).slice(8, -1);

            if (TYPEOF !== 'Function') {
                return console.warn('温馨提示：按钮未配置函数方法');
            }

            btn(
                // DCIForm的实例
                this,
                // 相关配置字段
                {
                    submodule: submodule
                }
            );
        }
    },
}
</script>

<style src="@assets/css/dci.form.css" scoped></style>
